home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 February: Tool Chest / Dev.CD Feb 94.toast / What's New? / Reference Library / Sample Code / Inline Input for TextEdit / InlineInputSample / InlineInputSample.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-12-09  |  66.5 KB  |  2,333 lines  |  [TEXT/MPS ]

  1. /*
  2.     File:        InlineInputSample.c
  3.  
  4.     Contains:    C source file for InlineInputSample
  5.  
  6.     Copyright:    © 1989, 1993 Apple Computer, Inc. All rights reserved.
  7.  
  8. */
  9.  
  10.  
  11. /* Segmentation strategy:
  12.  
  13.    There isn't any. Depending on which compiler you use, and whether you build for
  14.    Mac-OS 68K, A/UX, or PowerPC, segments may look different or not exist at all.
  15.    And with an application this small, segments don't really matter any more these
  16.    days. So we don't try to figure out segmentation and segment unloading strategies.
  17.    If you want to find out how to segment your application, look at some other
  18.    sample code.
  19. */
  20.  
  21.  
  22. /* SetPort strategy:
  23.  
  24.    Toolbox routines do not change the current port. In spite of this, in this
  25.    program we use a strategy of calling SetPort whenever we want to draw or
  26.    make calls which depend on the current port. This makes us less vulnerable
  27.    to bugs in other software which might alter the current port (such as the
  28.    bug (feature?) in many desk accessories which change the port on OpenDeskAcc).
  29.    Hopefully, this also makes the routines from this program more self-contained,
  30.    since they don't depend on the current port setting.
  31. */
  32.  
  33.  
  34. /* Clipboard strategy:
  35.  
  36.    Under styled TextEdit, TECut and TECopy will write both the text and associated
  37.    style information directly to the desk scrap as types 'TEXT' and 'styl'.
  38.    Instead of using TEToScrap and TEFromScrap, a new routine TEStylPaste, will 
  39.    transfer the text and style from the desk scrap to the document.
  40. */
  41.  
  42.  
  43. #if qInline
  44. #define qAppleEvents 1
  45. #endif
  46.  
  47.  
  48. #include <Values.h>
  49. #include <Types.h>
  50. #include <QuickDraw.h>
  51. #include <Fonts.h>
  52. #include <Events.h>
  53. #include <Controls.h>
  54. #include <Windows.h>
  55. #include <Menus.h>
  56. #include <TextEdit.h>
  57. #include <Dialogs.h>
  58. #include <Desk.h>
  59. #include <Scrap.h>
  60. #include <ToolUtils.h>
  61. #include <Memory.h>
  62. #include <SegLoad.h>
  63. #include <Files.h>
  64. #include <OSUtils.h>
  65. #include <OSEvents.h>
  66. #include <Packages.h>
  67. #include <Traps.h>
  68. #include <Printing.h>
  69. #include <DiskInit.h>
  70. #if qAppleEvents
  71. #include <Errors.h>
  72. #include <GestaltEqu.h>
  73. #include <AppleEvents.h>
  74. #if qInline
  75. #include <TextServices.h>
  76. #include <Script.h>
  77. #include <TSMTE.h>
  78. #endif // qInline
  79. #endif // qAppleEvents
  80.  
  81. #include "InlineInputSample.h"
  82.  
  83.  
  84. // Constants
  85.  
  86. // top left corner of the disk initialization dialog
  87.  
  88. const short kDITop = 80;
  89. const short kDILeft = 112;
  90.  
  91. // the number of pixels we leave blank at the edge of the window
  92.  
  93. const short kTextMargin = 2;
  94.  
  95. // the maximum number of open documents at any one time. SetupMenus respects this
  96. // number, but the Apple event handlers don't.
  97.  
  98. const short kMaxOpenDocuments = 4;
  99.     
  100. // arbitrary number used to specify the width of the TERec's destination
  101. // rectangle so that word wrap and horizontal scrolling can be demonstrated
  102.  
  103. const short kMaxDocWidth = 576;
  104.     
  105. // the minimum dimension of a window for GrowWindow
  106.  
  107. const short kMinDocDim = 64;
  108.  
  109. // control contrlVis values to prevent or enable redrawing controls by
  110. // Control Manager routines such as SetCtlValue
  111.  
  112. const unsigned char kControlInvisible = 0;
  113. const unsigned char kControlVisible = 0xff;
  114.  
  115. // for calculating scroll bar positions and sizes
  116.  
  117. const short kScrollbarWidth = 16;
  118. const short kScrollbarAdjust = 15; // should be kScrollbarWidth - 1, but C is too stupid
  119. const short kScrollTweek = 2;
  120.     
  121. // ASCII code for delete character
  122.  
  123. const unsigned char kDelChar = 8;
  124.     
  125. // pixels to scroll when the button part of the horizontal scrollbar is pressed
  126.  
  127. const short kButtonScroll = 4;
  128.  
  129. // maximum text length we allow in a TERec; lower than 32767 to prevent errors
  130.  
  131. const short kMaxTELength = 32000;
  132.  
  133. // the SysEnvRec version we understand
  134.  
  135. const short kSysEnvironsVersion = 1;
  136.  
  137. // events mask for no events
  138.  
  139. const short kNoEventsMask = 0;
  140.  
  141. // the minimum heap size and minimal available heap space we require for running.
  142. // These are rough guesses; with styled TextEdit and printing you never really know.
  143.  
  144. const Size kMinHeap = 50 * 1024;
  145. const Size kMinSpace = 40 * 1024;
  146.  
  147. // values for setting up wide open rectangles and regions
  148.  
  149. const short kExtremeNeg = -32768;
  150. const short kExtremePos = 32767 -1; // required to address an old region bug
  151.  
  152. // extra security when pre-flighting edit commands
  153.  
  154. const short kTESlop = 1024;
  155.  
  156.  
  157. // Types
  158.  
  159. // A DocumentRecord contains the WindowRecord for one of our document windows,
  160. // as well as the TEHandle for the text we are editing. Other document fields
  161. // can be added to this record as needed. This is similar to how the
  162. // Window Manager and Dialog Manager add fields after the GrafPort.
  163.  
  164. typedef struct {
  165.     WindowRecord    docWindow;
  166.     TEHandle        docTE;
  167.     ControlHandle    docVScroll;
  168.     ControlHandle    docHScroll;
  169.     TEClickLoopUPP    docClick;
  170.     Boolean            modified;
  171. #if qInline
  172.     TSMTERecHandle    docTSMTERecHandle;
  173.     TSMDocumentID    docTSMDoc;
  174. #endif
  175. } DocumentRecord, *DocumentPeek;
  176.  
  177.  
  178. // Global Variables
  179.  
  180. // environment information that's set up during initialization
  181.  
  182. Boolean gHasWaitNextEvent;        // WaitNextEvent trap is available
  183. #if qAppleEvents
  184. Boolean gHasAppleEvents;        // Apple events are available, so we expect to get events from the Finder
  185. #if qInline
  186. Boolean gHasTextServices;        // Text Services Manager is available and should be used
  187. Boolean gHasTSMTE;                // Text Services for Text Edit are available and should be used
  188.                                 // gHasTSMTE can only be set if gHasTextServices.
  189. #endif // qInline
  190. #endif // qAppleEvents
  191.  
  192. // whether we are currently in the background. This accounts for major switches only.
  193.  
  194. Boolean gInBackground;
  195.  
  196. // the number of documents currently open
  197.  
  198. short gNumDocuments;
  199.  
  200. // print record shared among all documents ??? probably should be attached to documents instead
  201.  
  202. THPrint gPrinterRecord;
  203.  
  204. // universal procedure pointers
  205.  
  206. ControlActionUPP gHActionUPP;
  207. ControlActionUPP gVActionUPP;
  208. #if powerc
  209. TEClickLoopUPP gClickLoopUPP;
  210. #endif
  211. #if qAppleEvents
  212. AEEventHandlerUPP gHandleOAppUPP;
  213. AEEventHandlerUPP gHandleDocUPP;
  214. AEEventHandlerUPP gHandleQuitUPP;
  215. #if qInline
  216. TSMTEPreUpdateUPP gTSMTEPreUpdateUPP;
  217. TSMTEPostUpdateUPP gTSMTEPostUpdateUPP;
  218. #endif // qInline
  219. #endif // qAppleEvents
  220.  
  221. #if powerc
  222. // PowerPC libraries don't automatically define QuickDraw globals
  223.  
  224. QDGlobals qd;
  225. #endif
  226.  
  227. #if qAppleEvents
  228. // to indicate that Quit command or Apple event was successful
  229.  
  230. Boolean gQuitting;
  231.  
  232. #if qInline
  233. // variable to keep outside fontForce information
  234.  
  235. long gSavedFontForce;
  236.  
  237. #endif // qInline
  238. #endif // qAppleEvents
  239.  
  240.  
  241. // Routine Declarations
  242.  
  243. void AlertUser(short error);
  244. void EventLoop(void);
  245. void DoEvent(EventRecord *event);
  246. void AdjustCursor(Point mouse, RgnHandle region);
  247. void GetGlobalMouse(Point *mouse);
  248. void DoGrowWindow(WindowPtr window, EventRecord *event);
  249. void DoZoomWindow(WindowPtr window, short part);
  250. void ResizeWindow(WindowPtr window);
  251. void GetLocalUpdateRgn(WindowPtr window, RgnHandle localRgn);
  252. void DoUpdate(WindowPtr window);
  253. void DoActivate(WindowPtr window, Boolean becomingActive);
  254. void DoContentClick(WindowPtr window, EventRecord *event);
  255. void DoKeyDown(EventRecord *event);
  256. unsigned long GetSleep(void);
  257. void CommonAction(ControlHandle control, short *amount);
  258. pascal void VActionProc(ControlHandle control, short part);
  259. pascal void HActionProc(ControlHandle control, short part);
  260. void DoIdle(void);
  261. void DrawWindow(WindowPtr window);
  262. void AdjustMenus(void);
  263. void DoMenuCommand(long menuResult);
  264. void DoNew(void);
  265. Boolean DoCloseWindow(WindowPtr window);
  266. #if qAppleEvents
  267. static void PrepareToQuit(void);
  268. #else // qAppleEvents
  269. static void Terminate(void);
  270. #endif // qAppleEvents
  271. void Initialize(void);
  272. void BigBadError(short error);
  273. static void FailNilUPP(UniversalProcPtr theUPP);
  274. void GetTERect(WindowPtr window, Rect *teRect);
  275. void AdjustViewRect(TEHandle docTE);
  276. void AdjustTE(WindowPtr window);
  277. void AdjustHV(Boolean isVert, ControlHandle control, TEHandle docTE, Boolean canRedraw);
  278. void AdjustScrollValues(WindowPtr window, Boolean canRedraw);
  279. void AdjustScrollSizes(WindowPtr window);
  280. void AdjustScrollbars(WindowPtr window, Boolean needsResize);
  281. #if powerc
  282. pascal Boolean ClickLoopProc(TEPtr pTE);
  283. #else
  284. extern pascal void AsmClickLoopProc(void);
  285. #endif
  286. pascal void ClickLoopAddOn(void);
  287. pascal TEClickLoopUPP GetOldClickLoop(void);
  288. Boolean IsDocumentWindow(WindowPtr window);
  289. Boolean IsDAWindow(WindowPtr window);
  290. Boolean TrapAvailable(short theTrap);
  291. static void PrintText(TEHandle theText);
  292. #if qAppleEvents
  293. static void CheckAppleEvents(void);
  294. static OSErr InstallRequiredAppleEvents(void);
  295. static OSErr GotRequiredParameters(const AppleEvent *theAppleEvent);
  296. pascal OSErr HandleOAppEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  297. pascal OSErr HandleDocEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  298. pascal OSErr HandleQuitEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  299. #if qInline
  300. static void CheckForTextServices(void);
  301. static pascal void MyTSMTEPreUpdateProc(TEHandle textH, long refCon);
  302. static pascal void MyTSMTEPostUpdateProc(TEHandle textH, long fixLen, long inputAreaStart,
  303.             long inputAreaEnd, long pinStart, long pinEnd, long refCon);
  304. static void ExitApplication(void);
  305. #endif // qInline
  306. #endif // qAppleEvents
  307.  
  308.  
  309.  
  310. // Set up the whole world, including global variables, Toolbox managers.
  311. // If a problem occurs here, we alert the user and exit from the application.
  312.  
  313. void Initialize(void)
  314. {
  315.     EventRecord event;
  316.     short count;
  317.     SysEnvRec systemEnvironment;
  318.     long total, contig;
  319.     Handle menuBar;
  320.  
  321.     gInBackground = false;
  322. #if qAppleEvents
  323.     gQuitting = false;
  324. #endif // qAppleEvents
  325.  
  326.     InitGraf((Ptr) &qd.thePort);
  327.     InitFonts();
  328.     InitWindows();
  329.     InitMenus();
  330.     TEInit();
  331.     InitDialogs(nil);
  332.     InitCursor();
  333.  
  334.     // the following loop is necessary to allow the default button of our
  335.     // alert to be outlined. We use EventAvail instead of GetNextEvent so we
  336.     // don't lose events.
  337.     
  338.     for (count = 1; count <= 3; count++)
  339.         EventAvail(everyEvent, &event);
  340.     
  341.     // collect environment information
  342.  
  343.     // ignore the error returned from SysEnvirons; even if an error occurred,
  344.     // the SysEnvirons glue will fill in the SysEnvRec.
  345.      
  346.     SysEnvirons(kSysEnvironsVersion, &systemEnvironment);
  347.     
  348.     // make sure that the machine has at least 128K ROMs. If it doesn't, exit.
  349.     
  350.     if (systemEnvironment.machineType < 0)
  351.         BigBadError(eOldROM);
  352.     
  353.     // also, require at least system 6.0. The app should be able to run on system
  354.     // software 4.1 (where styled TextEdit was introduced) and later, but it's not really
  355.     // worth the trouble of testing on all those old systems anymore.
  356.     
  357.     if (systemEnvironment.systemVersion < 0x600)
  358.         BigBadError(eOldSystemSoftware);
  359.     
  360.     // It is better to first check the size of the application heap against a value
  361.     // that you have determined is the smallest heap the application can reasonably
  362.     // work in. This number should be derived by examining the size of the heap that
  363.     // is actually provided by MultiFinder when the minimum size requested is used.
  364.     // The check should be made because the preferred size can end up being set smaller
  365.     // than the minimum size by the user. This extra check acts to insure that your
  366.     // application is starting from a solid memory foundation.
  367.      
  368.     if ((long) GetApplLimit() - (long) ApplicationZone() < kMinHeap)
  369.         BigBadError(eSmallSize);
  370.     
  371.     // Next, make sure that enough memory is free for your application to run. It
  372.     // is possible for a situation to arise where the heap may have been of required
  373.     // size, but a large scrap was loaded which left too little memory. To check for
  374.     // this, call PurgeSpace and compare the result with a value that you have determined
  375.     // is the minimum amount of free memory your application needs at initialization.
  376.     // This number can be derived several different ways. One way that is fairly
  377.     // straightforward is to run the application in the minimum size configuration
  378.     // as described previously. Call PurgeSpace at initialization and examine the value
  379.     // returned. However, you should make sure that this result is not being modified
  380.     // by the scrap's presence. You can do that by calling ZeroScrap before calling
  381.     // PurgeSpace. Make sure to remove that call before shipping, though.
  382.     
  383.     // ZeroScrap();
  384.  
  385.     PurgeSpace(&total, &contig);
  386.     if (total < kMinSpace)
  387.         if (UnloadScrap() != noErr)
  388.             BigBadError(eNoMemory);
  389.         else
  390.         {
  391.             PurgeSpace(&total, &contig);
  392.             if (total < kMinSpace)
  393.                 BigBadError(eNoMemory);
  394.         };
  395.  
  396.     // The extra benefit to waiting until after the Toolbox Managers have been initialized
  397.     // to check memory is that we can now give the user an alert to tell him/her what
  398.     // happened. Although it is possible that the memory situation could be worsened by
  399.     // displaying an alert, MultiFinder would gracefully exit the application with
  400.     // an informative alert if memory became critical. Here we are acting more
  401.     // in a preventative manner to avoid future disaster from low-memory problems.
  402.  
  403.     // check for newer system services and set up our environment to make use of what's available.
  404.     
  405.     gHasWaitNextEvent = TrapAvailable(_WaitNextEvent);
  406.  
  407. #if qAppleEvents
  408.     CheckAppleEvents();
  409.     if (gHasAppleEvents)
  410.         (void) InstallRequiredAppleEvents();
  411. #if qInline
  412.  
  413.     CheckForTextServices();
  414.     
  415.     // this application uses TextEdit as the only text engine, and we don't support
  416.     // inline input without TSMTE. Therefore we call InitTSMAwareApplication only if
  417.     // TSMTE is available. A word processor that uses TextEdit only for dialogs
  418.     // and uses Text Services directly with the word processing engine would make this
  419.     // call depend on gHasTextServices.
  420.     
  421.     if (!(gHasTSMTE && InitTSMAwareApplication() == noErr))
  422.     {
  423.         // if this happens, just move on without text services
  424.         gHasTextServices = false;
  425.         gHasTSMTE = false;
  426.     };
  427.     
  428.     // get global fontForce flag, make sure it's off whenever we run
  429.     
  430.     gSavedFontForce = GetScriptManagerVariable(smFontForce);
  431.     (void) SetScriptManagerVariable(smFontForce, 0);
  432. #endif // qInline
  433. #endif // qAppleEvents
  434.  
  435.     // set up the menu bar and the menus that depend on the system environment
  436.     
  437.     menuBar = GetNewMBar(rMenuBar);    
  438.     if ( menuBar == nil )
  439.         BigBadError(eNoMemory);
  440.     SetMenuBar(menuBar);
  441.     DisposeHandle(menuBar);
  442.     AppendResMenu(GetMenuHandle(mApple), 'DRVR');    // build the Apple menu
  443.     AppendResMenu(GetMenuHandle(mFont), 'FONT');    // build the Font menu
  444.     DrawMenuBar();
  445.  
  446.     // we have no document open yet
  447.     
  448.     gNumDocuments = 0;
  449.  
  450.     // set up printer stuff - this will allow the default page setup parameters to be used,
  451.     // so if the user decides to print without using the Page Setup command everything will
  452.     // be OK
  453.     
  454.     gPrinterRecord = (THPrint) NewHandle(sizeof(TPrint));
  455.     if (gPrinterRecord != nil)
  456.     {
  457.         // if we got a print handle, initialize it to default values
  458.         PrOpen();
  459.         PrintDefault(gPrinterRecord);
  460.         PrClose();
  461.     };
  462.     
  463.     // initialize the universal procedure pointers that we need
  464. #if qAppleEvents
  465.     // (Apple event handler UPPs are set up in InstallRequiredAppleEvents)
  466. #endif // qAppleEvents
  467.     
  468.     gHActionUPP = NewControlActionProc(HActionProc);
  469.     FailNilUPP((UniversalProcPtr) gHActionUPP);
  470.     gVActionUPP = NewControlActionProc(VActionProc);
  471.     FailNilUPP((UniversalProcPtr) gVActionUPP);
  472. #if powerc
  473.     gClickLoopUPP = NewTEClickLoopProc(ClickLoopProc);
  474.     FailNilUPP((UniversalProcPtr) gClickLoopUPP);
  475. #endif
  476. #if qInline
  477.     if (gHasTSMTE)
  478.     {
  479.         gTSMTEPreUpdateUPP = NewTSMTEPreUpdateProc(MyTSMTEPreUpdateProc);
  480.         FailNilUPP((UniversalProcPtr) gTSMTEPreUpdateUPP);
  481.         gTSMTEPostUpdateUPP = NewTSMTEPostUpdateProc(MyTSMTEPostUpdateProc);
  482.         FailNilUPP((UniversalProcPtr) gTSMTEPostUpdateUPP);
  483.     };
  484. #endif // qInline
  485. }
  486.  
  487.  
  488. // report a fatal error to the user and exit from the application
  489.  
  490. void BigBadError(short error)
  491. {
  492.     AlertUser(error);
  493. #if qInline
  494.     ExitApplication();
  495. #else // qInline
  496.     ExitToShell();
  497. #endif // qInline
  498. }
  499.  
  500.  
  501. // check whether a valid UPP was allocated
  502.  
  503. static void FailNilUPP(UniversalProcPtr theUPP)
  504. {
  505.     if (theUPP == nil)
  506.         BigBadError(eNoMemory);
  507. }
  508.  
  509. #if qAppleEvents
  510.  
  511. // check to see if a given bit in a long word is set.
  512.  
  513. static Boolean BTst(long value, short bit)
  514. {
  515.     long mask = 1L << bit;
  516.     
  517.     return (value & mask) == mask;
  518. }
  519.  
  520. #endif // qAppleEvents
  521.  
  522. // check to see if a given trap is implemented. We follow IM VI-3-8.
  523.  
  524. Boolean TrapAvailable(short theTrap)
  525. {
  526.     TrapType theTrapType;
  527.     short numToolboxTraps;
  528.     
  529.     if ((theTrap & 0x0800) > 0)
  530.         theTrapType = ToolTrap;
  531.     else
  532.         theTrapType = OSTrap;
  533.  
  534.     if (theTrapType == ToolTrap)
  535.     {
  536.         theTrap = theTrap & 0x07ff;
  537.         if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xaa6e, ToolTrap))
  538.             numToolboxTraps = 0x0200;
  539.         else
  540.             numToolboxTraps = 0x0400;
  541.         if (theTrap >= numToolboxTraps)
  542.             theTrap = _Unimplemented;
  543.     };
  544.  
  545.     return (NGetTrapAddress(theTrap, theTrapType) != NGetTrapAddress(_Unimplemented, ToolTrap));
  546. }
  547.  
  548. #if qAppleEvents
  549.  
  550. static void CheckAppleEvents(void)
  551. {
  552.     long gestaltResponse;
  553.     
  554.     gHasAppleEvents = false;
  555.     
  556.     if (TrapAvailable(_Gestalt))
  557.     {
  558.         if (Gestalt(gestaltAppleEventsAttr, &gestaltResponse) == noErr)
  559.             gHasAppleEvents = BTst(gestaltResponse, gestaltAppleEventsPresent);
  560.     };
  561. }
  562.  
  563. #if qInline
  564.  
  565. // check whether the Text Services Manager and the extension for using Text Services with
  566. // TextEdit (TSMTE) are available, and sets gHasTextServices and gHasTSMTE accordingly.
  567.  
  568. static void CheckForTextServices(void)
  569. {
  570.     long gestaltResponse;
  571.     
  572.     gHasTextServices = false;        // unless proven otherwise
  573.     gHasTSMTE = false;                // unless proven otherwise
  574.     
  575.     if (TrapAvailable(_Gestalt))
  576.     {
  577.         if ((Gestalt(gestaltTSMgrVersion, &gestaltResponse) == noErr) && (gestaltResponse >= 1))
  578.         {
  579.             gHasTextServices = true;
  580.             if (Gestalt(gestaltTSMTEAttr, &gestaltResponse) == noErr)
  581.                 gHasTSMTE = BTst(gestaltResponse, gestaltTSMTEPresent);
  582.         };
  583.     };
  584. }
  585.  
  586. #endif // qInline
  587. #endif // qAppleEvents
  588.  
  589. void main(void)
  590. {
  591.     /*    If you have stack requirements that differ from the default,
  592.         then you could use SetApplLimit to increase StackSpace at 
  593.         this point, before calling MaxApplZone. */
  594.     MaxApplZone();                    /* expand the heap */
  595.  
  596.     Initialize();                    /* initialize the program */
  597. #if qAppleEvents
  598.     if (!gHasAppleEvents)
  599.         DoNew();
  600. #else // qAppleEvents
  601.     DoNew();
  602. #endif // qAppleEvents
  603.  
  604.     EventLoop();                    /* call the main event loop */
  605.  
  606. #if qAppleEvents
  607. #if qInline
  608.     ExitApplication();
  609. #else // qInline
  610.     ExitToShell();
  611. #endif // qInline
  612. #endif // qAppleEvents
  613. }
  614.  
  615. #if qInline
  616.  
  617. static Boolean IntlTSMEvent(EventRecord *event)
  618. {
  619.     short oldFont;
  620.     ScriptCode keyboardScript;
  621.     
  622.     if (qd.thePort != nil)
  623.     {
  624.         oldFont = qd.thePort->txFont;
  625.         keyboardScript = GetScriptManagerVariable(smKeyScript);
  626.         if (FontToScript(oldFont) != keyboardScript)
  627.             TextFont(GetScriptVariable(keyboardScript, smScriptAppFond));
  628.     };
  629.     return TSMEvent(event);
  630. }
  631.  
  632. #endif // qInline
  633.  
  634. /* Get events forever, and handle them by calling DoEvent.
  635.    Also call AdjustCursor each time through the loop. */
  636.  
  637. void EventLoop(void)
  638. {
  639.     RgnHandle    cursorRgn;
  640.     Boolean        gotEvent;
  641.     EventRecord    event;
  642.     Point        mouse;
  643.  
  644.     cursorRgn = NewRgn();            /* we’ll pass WNE an empty region the 1st time thru */
  645. #if qAppleEvents
  646.     while (!gQuitting)
  647. #else // qAppleEvents
  648.     while (true) // loop forever, quit via ExitToShell
  649. #endif // qAppleEvents
  650.     {
  651. #if qInline
  652.         // set global fontForce flag so other apps don't get confused
  653.         (void) SetScriptManagerVariable(smFontForce, gSavedFontForce);
  654.         
  655. #endif // qInline
  656.         /* use WNE if it is available */
  657.         if ( gHasWaitNextEvent ) {
  658.             GetGlobalMouse(&mouse);
  659.             AdjustCursor(mouse, cursorRgn);
  660.             gotEvent = WaitNextEvent(everyEvent, &event, GetSleep(), cursorRgn);
  661.         }
  662.         else {
  663.             SystemTask();
  664.             gotEvent = GetNextEvent(everyEvent, &event);
  665.         };
  666.  
  667. #if qInline
  668.         // clear fontForce again so it doesn't upset our operations
  669.         gSavedFontForce = GetScriptManagerVariable(smFontForce);
  670.         (void) SetScriptManagerVariable(smFontForce, 0);
  671.         
  672.         if ( gotEvent && !(gHasTextServices && IntlTSMEvent(&event)))
  673. #else // qInline
  674.         if ( gotEvent )
  675. #endif
  676.         {
  677.             /* make sure we have the right cursor before handling the event */
  678.             AdjustCursor(event.where, cursorRgn);
  679.             DoEvent(&event);
  680.         }
  681.         else
  682.             DoIdle();                /* perform idle tasks when it’s not our event */
  683.         /*    If you are using modeless dialogs that have editText items,
  684.             you will want to call IsDialogEvent to give the caret a chance
  685.             to blink, even if WNE/GNE returned FALSE. However, check FrontWindow
  686.             for a non-NIL value before calling IsDialogEvent. */
  687.     };
  688. }
  689.  
  690.  
  691. /* Do the right thing for an event. Determine what kind of event it is, and call
  692.  the appropriate routines. */
  693.  
  694. void DoEvent(EventRecord *event)
  695. {
  696.     short        part, err;
  697.     WindowPtr    window;
  698.     char        key;
  699.     Point        aPoint;
  700. #if qInline
  701.     long        menuResult;
  702. #endif // qInline
  703.  
  704.     switch ( event->what ) {
  705.         case nullEvent:
  706.             /* we idle for null/mouse moved events ands for events which aren’t
  707.                 ours (see EventLoop) */
  708.             DoIdle();
  709.             break;
  710.         case mouseDown:
  711.             part = FindWindow(event->where, &window);
  712.             switch ( part ) {
  713.                 case inMenuBar:             /* process a mouse menu command (if any) */
  714.                     AdjustMenus();
  715. #if qInline
  716.                     menuResult = MenuSelect(event->where);
  717.                     if (!(gHasTextServices && TSMMenuSelect(menuResult)))
  718.                         DoMenuCommand(menuResult);
  719.                     HiliteMenu(0); // needed even if TSM or Script Manager handle the menu
  720. #else // qInline
  721.                     DoMenuCommand(MenuSelect(event->where));
  722. #endif // qInline
  723.                     break;
  724.                 case inSysWindow:           /* let the system handle the mouseDown */
  725.                     SystemClick(event, window);
  726.                     break;
  727.                 case inContent:
  728.                     if ( window != FrontWindow() ) {
  729.                         SelectWindow(window);
  730.                         AdjustMenus();
  731.                     } else
  732.                         DoContentClick(window, event);
  733.                     break;
  734.                 case inDrag:                /* pass screenBits.bounds to get all gDevices */
  735.                     DragWindow(window, event->where, &qd.screenBits.bounds);
  736.                     break;
  737.                 case inGoAway:
  738.                     if ( TrackGoAway(window, event->where) )
  739.                         DoCloseWindow(window); /* we don’t care if the user cancelled */
  740.                     break;
  741.                 case inGrow:
  742.                     DoGrowWindow(window, event);
  743.                     break;
  744.                 case inZoomIn:
  745.                 case inZoomOut:
  746.                 if ( TrackBox(window, event->where, part) )
  747.                         DoZoomWindow(window, part);
  748.                     break;
  749.             }
  750.             break;
  751.         case keyDown:
  752.         case autoKey:                       /* check for menukey equivalents */
  753.             key = event->message & charCodeMask;
  754.             if ( event->modifiers & cmdKey ) {    /* Command key down */
  755.                 if ( event->what == keyDown ) {
  756.                     AdjustMenus();            /* enable/disable/check menu items properly */
  757.                     DoMenuCommand(MenuKey(key));
  758.                 }
  759.             } else
  760.                 DoKeyDown(event);
  761.             break;
  762.         case activateEvt:
  763.             DoActivate((WindowPtr) event->message, (event->modifiers & activeFlag) != 0);
  764.             break;
  765.         case updateEvt:
  766.             DoUpdate((WindowPtr) event->message);
  767.             break;
  768.         /*    1.01 - It is not a bad idea to at least call DIBadMount in response
  769.             to a diskEvt, so that the user can format a floppy. */
  770.         case diskEvt:
  771.             if ( HiWord(event->message) != noErr ) {
  772.                 SetPt(&aPoint, kDILeft, kDITop);
  773.                 err = DIBadMount(aPoint, event->message);
  774.             }
  775.             break;
  776.         case osEvt:
  777.         /*    1.02 - must BitAND with 0x0FF to get only low byte */
  778.             switch ((event->message >> 24) & 0x0FF) {        /* high byte of message */
  779.                 case mouseMovedMessage:
  780.                     DoIdle();                    /* mouse-moved is also an idle event */
  781.                     break;
  782.                 case suspendResumeMessage:        /* suspend/resume is also an activate/deactivate */
  783.                     gInBackground = (event->message & resumeFlag) == 0;
  784.                     DoActivate(FrontWindow(), !gInBackground);
  785.                     break;
  786.             }
  787.             break;
  788. #if qAppleEvents
  789.         case kHighLevelEvent:
  790.             if (AEProcessAppleEvent(event) != noErr)
  791.                 ; // any ideas for error handling?
  792.             break;
  793. #endif // qAppleEvents
  794.     }
  795. } /*DoEvent*/
  796.  
  797.  
  798. /*    Change the cursor's shape, depending on its position. This also calculates the region
  799.     where the current cursor resides (for WaitNextEvent). When the mouse moves outside of
  800.     this region, an event is generated. If there is more to the event than just
  801.     “the mouse moved”, we get called before the event is processed to make sure
  802.     the cursor is the right one. In any (ahem) event, this is called again before we
  803.     fall back into WNE. */
  804.  
  805. void AdjustCursor(Point mouse, RgnHandle region)
  806. {
  807.     WindowPtr    window;
  808.     RgnHandle    arrowRgn;
  809.     RgnHandle    iBeamRgn;
  810.     Rect        iBeamRect;
  811.  
  812.     window = FrontWindow();    /* we only adjust the cursor when we are in front */
  813.     if ( (! gInBackground) && (! IsDAWindow(window)) ) {
  814.         /* calculate regions for different cursor shapes */
  815.         arrowRgn = NewRgn();
  816.         iBeamRgn = NewRgn();
  817.  
  818.         /* start arrowRgn wide open */
  819.         SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
  820.  
  821.         /* calculate iBeamRgn */
  822.         if ( IsDocumentWindow(window) ) {
  823.             iBeamRect = (*((DocumentPeek) window)->docTE)->viewRect;
  824.             SetPort(window);    /* make a global version of the viewRect */
  825.             // ??? following two lines depend on Rect structure layout
  826.             LocalToGlobal((Point *) &(iBeamRect).top);
  827.             LocalToGlobal((Point *) &(iBeamRect).bottom);
  828.             RectRgn(iBeamRgn, &iBeamRect);
  829.             /* we temporarily change the port’s origin to “globalfy” the visRgn */
  830.             SetOrigin(-window->portBits.bounds.left, -window->portBits.bounds.top);
  831.             SectRgn(iBeamRgn, window->visRgn, iBeamRgn);
  832.             SetOrigin(0, 0);
  833.         }
  834.  
  835.         /* subtract other regions from arrowRgn */
  836.         DiffRgn(arrowRgn, iBeamRgn, arrowRgn);
  837.  
  838. #if qInline
  839.         // before we commit to anything, let's check whether some text service has a
  840.         // different idea
  841.         
  842.         if (!(gHasTextServices && SetTSMCursor(mouse)))
  843.         {
  844.             // change the cursor and the region parameter
  845.             if (PtInRgn(mouse, iBeamRgn))
  846.             {
  847.                 SetCursor(*GetCursor(iBeamCursor));
  848.                 CopyRgn(iBeamRgn, region);
  849.             }
  850.             else
  851.             {
  852.                 SetCursor(&qd.arrow);
  853.                 CopyRgn(arrowRgn, region);
  854.             };
  855.         };
  856.  
  857.         // and no matter how nice the region, with text services it cannot be bigger than
  858.         // a point. Yes, this defeats the purpose of all the calculations...
  859.         
  860.         if (gHasTextServices)
  861.             SetRectRgn(region, mouse.h, mouse.v, mouse.h, mouse.v);
  862. #else // qInline
  863.         // change the cursor and the region parameter
  864.         if (PtInRgn(mouse, iBeamRgn))
  865.         {
  866.             SetCursor(*GetCursor(iBeamCursor));
  867.             CopyRgn(iBeamRgn, region);
  868.         }
  869.         else
  870.         {
  871.             SetCursor(&qd.arrow);
  872.             CopyRgn(arrowRgn, region);
  873.         };
  874. #endif // qInline
  875.  
  876.         DisposeRgn(arrowRgn);
  877.         DisposeRgn(iBeamRgn);
  878.     }
  879. } /*AdjustCursor*/
  880.  
  881.  
  882. /*    Get the global coordinates of the mouse. When you call OSEventAvail
  883.     it will return either a pending event or a null event. In either case,
  884.     the where field of the event record will contain the current position
  885.     of the mouse in global coordinates and the modifiers field will reflect
  886.     the current state of the modifiers. Another way to get the global
  887.     coordinates is to call GetMouse and LocalToGlobal, but that requires
  888.     being sure that thePort is set to a valid port. */
  889.  
  890. void GetGlobalMouse(Point *mouse)
  891. {
  892.     EventRecord    event;
  893.     
  894.     OSEventAvail(kNoEventsMask, &event);    /* we aren't interested in any events */
  895.     *mouse = event.where;                /* just the mouse position */
  896. } /*GetGlobalMouse*/
  897.  
  898.  
  899. /*    Called when a mouseDown occurs in the grow box of an active window. In
  900.     order to eliminate any 'flicker', we want to invalidate only what is
  901.     necessary. Since ResizeWindow invalidates the whole portRect, we save
  902.     the old TE viewRect, intersect it with the new TE viewRect, and
  903.     remove the result from the update region. However, we must make sure
  904.     that any old update region that might have been around gets put back. */
  905.  
  906. void DoGrowWindow(WindowPtr window, EventRecord *event)
  907. {
  908.     long        growResult;
  909.     Rect        tempRect;
  910.     RgnHandle    tempRgn;
  911.     DocumentPeek doc;
  912.     
  913.     tempRect = qd.screenBits.bounds;                    /* set up limiting values */
  914.     tempRect.left = kMinDocDim;
  915.     tempRect.top = kMinDocDim;
  916.     growResult = GrowWindow(window, event->where, &tempRect);
  917.     /* see if it really changed size */
  918.     if ( growResult != 0 ) {
  919.         doc = (DocumentPeek) window;
  920.         tempRect = (*doc->docTE)->viewRect;                /* save old text box */
  921.         tempRgn = NewRgn();
  922.         GetLocalUpdateRgn(window, tempRgn);                /* get localized update region */
  923.         SizeWindow(window, LoWord(growResult), HiWord(growResult), true);
  924.         ResizeWindow(window);
  925.         /* calculate & validate the region that hasn’t changed so it won’t get redrawn */
  926.         SectRect(&tempRect, &(*doc->docTE)->viewRect, &tempRect);
  927.         ValidRect(&tempRect);                            /* take it out of update */
  928.         InvalRgn(tempRgn);                                /* put back any prior update */
  929.         DisposeRgn(tempRgn);
  930.     }
  931. } /* DoGrowWindow */
  932.  
  933.  
  934. /*     Called when a mouseClick occurs in the zoom box of an active window.
  935.     Everything has to get re-drawn here, so we don't mind that
  936.     ResizeWindow invalidates the whole portRect. */
  937.  
  938. void DoZoomWindow(WindowPtr window, short part)
  939. {
  940.     EraseRect(&window->portRect);
  941.     ZoomWindow(window, part, window == FrontWindow());
  942.     ResizeWindow(window);
  943. } /*  DoZoomWindow */
  944.  
  945.  
  946. /* Called when the window has been resized to fix up the controls and content. */
  947. void ResizeWindow(WindowPtr window)
  948. {
  949.     AdjustScrollbars(window, true);
  950.     AdjustTE(window);
  951.     InvalRect(&window->portRect);
  952. } /* ResizeWindow */
  953.  
  954.  
  955. /* Returns the update region in local coordinates */
  956. void GetLocalUpdateRgn(WindowPtr window, RgnHandle localRgn)
  957. {
  958.     CopyRgn(((WindowPeek) window)->updateRgn, localRgn);    /* save old update region */
  959.     OffsetRgn(localRgn, window->portBits.bounds.left, window->portBits.bounds.top);
  960. } /* GetLocalUpdateRgn */
  961.  
  962.  
  963. /*    This is called when an update event is received for a window.
  964.     It calls DrawWindow to draw the contents of an application window.
  965.     As an efficiency measure that does not have to be followed, it
  966.     calls the drawing routine only if the visRgn is non-empty. This
  967.     will handle situations where calculations for drawing or drawing
  968.     itself is very time-consuming. */
  969.  
  970. void DoUpdate(WindowPtr window)
  971. {
  972.     if ( IsDocumentWindow(window) ) {
  973.         BeginUpdate(window);                /* this sets up the visRgn */
  974.         if ( ! EmptyRgn(window->visRgn) )    /* draw if updating needs to be done */
  975.             DrawWindow(window);
  976.         EndUpdate(window);
  977.     }
  978. } /*DoUpdate*/
  979.  
  980.  
  981. /*    This is called when a window is activated or deactivated.
  982.     It calls TextEdit to deal with the selection. */
  983.  
  984. void DoActivate(WindowPtr window, Boolean becomingActive)
  985. {
  986.     RgnHandle    tempRgn, clipRgn;
  987.     Rect        growRect;
  988.     DocumentPeek doc;
  989.     
  990.     if ( IsDocumentWindow(window) ) {
  991.         doc = (DocumentPeek) window;
  992.         if ( becomingActive ) {
  993.             /*    since we don’t want TEActivate to draw a selection in an area where
  994.                 we’re going to erase and redraw, we’ll clip out the update region
  995.                 before calling it. */
  996.             tempRgn = NewRgn();
  997.             clipRgn = NewRgn();
  998.             GetLocalUpdateRgn(window, tempRgn);            /* get localized update region */
  999.             GetClip(clipRgn);
  1000.             DiffRgn(clipRgn, tempRgn, tempRgn);            /* subtract updateRgn from clipRgn */
  1001.             SetClip(tempRgn);
  1002.             TEActivate(doc->docTE);
  1003.             SetClip(clipRgn);                            /* restore the full-blown clipRgn */
  1004.             DisposeRgn(tempRgn);
  1005.             DisposeRgn(clipRgn);
  1006.             
  1007.             /* the controls must be redrawn on activation: */
  1008.             (*doc->docVScroll)->contrlVis = kControlVisible;
  1009.             (*doc->docHScroll)->contrlVis = kControlVisible;
  1010.             InvalRect(&(*doc->docVScroll)->contrlRect);
  1011.             InvalRect(&(*doc->docHScroll)->contrlRect);
  1012.             /* the growbox needs to be redrawn on activation: */
  1013.             growRect = window->portRect;
  1014.             /* adjust for the scrollbars */
  1015.             growRect.top = growRect.bottom - kScrollbarAdjust;
  1016.             growRect.left = growRect.right - kScrollbarAdjust;
  1017.             InvalRect(&growRect);
  1018. #if qInline
  1019.             if (doc->docTSMDoc != nil)
  1020.                 (void) ActivateTSMDocument(doc->docTSMDoc);
  1021. #endif // qInline
  1022.         }
  1023.         else
  1024.         {        
  1025. #if qInline
  1026.             if (doc->docTSMDoc != nil)
  1027.                 (void) DeactivateTSMDocument(doc->docTSMDoc);
  1028. #endif // qInline
  1029.             TEDeactivate(doc->docTE);
  1030.             /* the controls must be hidden on deactivation: */
  1031.             HideControl(doc->docVScroll);
  1032.             HideControl(doc->docHScroll);
  1033.             /* the growbox should be changed immediately on deactivation: */
  1034.             DrawGrowIcon(window);
  1035.         }
  1036.     }
  1037. } /*DoActivate*/
  1038.  
  1039.  
  1040. /*    This is called when a mouseDown occurs in the content of a window. */
  1041.  
  1042. void DoContentClick(WindowPtr window, EventRecord *event)
  1043. {
  1044.     Point        mouse;
  1045.     ControlHandle control;
  1046.     short        part, value;
  1047.     Boolean        shiftDown;
  1048.     DocumentPeek doc;
  1049.     Rect        teRect;
  1050.  
  1051.     if ( IsDocumentWindow(window) ) {
  1052.         SetPort(window);
  1053.         mouse = event->where;                            /* get the click position */
  1054.         GlobalToLocal(&mouse);
  1055.         doc = (DocumentPeek) window;
  1056.         /* see if we are in the viewRect. if so, we won’t check the controls */
  1057.         GetTERect(window, &teRect);
  1058.         if ( PtInRect(mouse, &teRect) ) {
  1059.             /* see if we need to extend the selection */
  1060.             shiftDown = (event->modifiers & shiftKey) != 0;    /* extend if Shift is down */
  1061.             TEClick(mouse, shiftDown, doc->docTE);
  1062.         } else {
  1063.             part = FindControl(mouse, window, &control);
  1064.             switch ( part ) {
  1065.                 case 0:                            /* do nothing for viewRect case */
  1066.                     break;
  1067.                 case inThumb:
  1068.                     value = GetControlValue(control);
  1069.                     part = TrackControl(control, mouse, nil);
  1070.                     if ( part != 0 ) {
  1071.                         value -= GetControlValue(control);
  1072.                         /* value now has CHANGE in value; if value changed, scroll */
  1073.                         if ( value != 0 )
  1074.                             if ( control == doc->docVScroll )
  1075.                                 TEScroll(0, value, doc->docTE);
  1076.                             else
  1077.                                 TEScroll(value, 0, doc->docTE);
  1078.                     }
  1079.                     break;
  1080.                 default:                        /* they clicked in an arrow, so track & scroll */
  1081.                     if ( control == doc->docVScroll )
  1082.                         value = TrackControl(control, mouse, gVActionUPP);
  1083.                     else
  1084.                         value = TrackControl(control, mouse, gHActionUPP);
  1085.                     break;
  1086.             }
  1087.         }
  1088.     }
  1089. } /*DoContentClick*/
  1090.  
  1091.  
  1092. /* This is called for any keyDown or autoKey events, except when the
  1093.  Command key is held down. It looks at the frontmost window to decide what
  1094.  to do with the key typed. */
  1095.  
  1096. void DoKeyDown(EventRecord *event)
  1097. {
  1098.     enum { minArrowKey = 28, maxArrowKey = 31 };
  1099.  
  1100.     WindowPtr    window;
  1101.     char        key;
  1102.     TEHandle    te;
  1103.  
  1104.     window = FrontWindow();
  1105.     if ( IsDocumentWindow(window) ) {
  1106.         te = ((DocumentPeek) window)->docTE;
  1107.         key = event->message & charCodeMask;
  1108.         /* we have a char. for our window; see if we are still below TextEdit’s
  1109.             limit for the number of characters (but deletes are always rad) */
  1110.         if ( key == kDelChar ||
  1111.                 (*te)->teLength - ((*te)->selEnd - (*te)->selStart) + 1 <
  1112.                 kMaxTELength )
  1113.         {
  1114.             TEKey(key, te);
  1115.             if ((key < minArrowKey) || (key > maxArrowKey)) // not a cursor key
  1116.                 ((DocumentPeek) window)->modified = true;
  1117.             AdjustScrollbars(window, false);
  1118.         }
  1119.         else
  1120.             AlertUser(eExceedChar);
  1121.     }
  1122. } /*DoKeyDown*/
  1123.  
  1124.  
  1125. /*    Calculate a sleep value for WaitNextEvent. This takes into account the things
  1126.     that DoIdle does with idle time. */
  1127.  
  1128. unsigned long GetSleep(void)
  1129. {
  1130.     long        sleep;
  1131.     WindowPtr    window;
  1132.     TEHandle    te;
  1133.  
  1134.     sleep = MAXLONG;                        /* default value for sleep */
  1135.     if ( !gInBackground ) {
  1136.         window = FrontWindow();            /* and the front window is ours... */
  1137.         if ( IsDocumentWindow(window) ) {
  1138.             te = ((DocumentPeek) (window))->docTE;    /* and the selection is an insertion point... */
  1139.             if ( (*te)->selStart == (*te)->selEnd )
  1140.                 sleep = GetCaretTime();        /* blink time for the insertion point */
  1141.         }
  1142.     }
  1143.     return sleep;
  1144. } /*GetSleep*/
  1145.  
  1146.  
  1147. /*    Common algorithm for pinning the value of a control. It returns the actual amount
  1148.     the value of the control changed. Note the pinning is done for the sake of returning
  1149.     the amount the control value changed. */
  1150.  
  1151. void CommonAction(ControlHandle control, short *amount)
  1152. {
  1153.     short        value, max;
  1154.     
  1155.     value = GetControlValue(control);    /* get current value */
  1156.     max = GetControlMaximum(control);        /* and maximum value */
  1157.     *amount = value - *amount;
  1158.     if ( *amount < 0 )
  1159.         *amount = 0;
  1160.     else if ( *amount > max )
  1161.         *amount = max;
  1162.     SetControlValue(control, *amount);
  1163.     *amount = value - *amount;        /* calculate the real change */
  1164. } /* CommonAction */
  1165.  
  1166.  
  1167. /* Determines how much to change the value of the vertical scrollbar by and how
  1168.     much to scroll the TE record. */
  1169.  
  1170. pascal void VActionProc(ControlHandle control, short part)
  1171. {
  1172.     short        amount;
  1173.     WindowPtr    window;
  1174.     TEPtr        te;
  1175.     
  1176.     if ( part != 0 ) {                /* if it was actually in the control */
  1177.         window = (*control)->contrlOwner;
  1178.         te = *((DocumentPeek) window)->docTE;
  1179.         switch ( part ) {
  1180.             case inUpButton:
  1181.             case inDownButton:
  1182.                 amount = 24;
  1183.                 break;
  1184.             case inPageUp:            /* one page */
  1185.             case inPageDown:
  1186.                 amount = te->viewRect.bottom - te->viewRect.top;
  1187.                 break;
  1188.         }
  1189.         if ( (part == inDownButton) || (part == inPageDown) )
  1190.             amount = -amount;        /* reverse direction for a downer */
  1191.         CommonAction(control, &amount);
  1192.         if ( amount != 0 )
  1193.             TEScroll(0, amount, ((DocumentPeek) window)->docTE);
  1194.     }
  1195. } /* VActionProc */
  1196.  
  1197.  
  1198. /* Determines how much to change the value of the horizontal scrollbar by and how
  1199. much to scroll the TE record. */
  1200.  
  1201. pascal void HActionProc(ControlHandle control, short part)
  1202. {
  1203.     short        amount;
  1204.     WindowPtr    window;
  1205.     TEPtr        te;
  1206.     
  1207.     if ( part != 0 ) {
  1208.         window = (*control)->contrlOwner;
  1209.         te = *((DocumentPeek) window)->docTE;
  1210.         switch ( part ) {
  1211.             case inUpButton:
  1212.             case inDownButton:        /* a few pixels */
  1213.                 amount = kButtonScroll;
  1214.                 break;
  1215.             case inPageUp:            /* a page */
  1216.             case inPageDown:
  1217.                 amount = te->viewRect.right - te->viewRect.left;
  1218.                 break;
  1219.         }
  1220.         if ( (part == inDownButton) || (part == inPageDown) )
  1221.             amount = -amount;        /* reverse direction */
  1222.         CommonAction(control, &amount);
  1223.         if ( amount != 0 )
  1224.             TEScroll(amount, 0, ((DocumentPeek) window)->docTE);
  1225.     }
  1226. } /* VActionProc */
  1227.  
  1228.  
  1229. /* This is called whenever we get a null event et al.
  1230.  It takes care of necessary periodic actions. For this program, it calls TEIdle. */
  1231.  
  1232. void DoIdle(void)
  1233. {
  1234.     WindowPtr    window;
  1235.  
  1236.     window = FrontWindow();
  1237.     if ( IsDocumentWindow(window) )
  1238.         TEIdle(((DocumentPeek) window)->docTE);
  1239. } /*DoIdle*/
  1240.  
  1241.  
  1242. /* Draw the contents of an application window. */
  1243.  
  1244. void DrawWindow(WindowPtr window)
  1245. {
  1246.     SetPort(window);
  1247.     EraseRect(&window->portRect);
  1248.     DrawControls(window);
  1249.     DrawGrowIcon(window);
  1250.     TEUpdate(&window->portRect, ((DocumentPeek) window)->docTE);
  1251. } /*DrawWindow*/
  1252.  
  1253.  
  1254. // menu handling utilities
  1255.  
  1256. static void DisableMenu(MenuHandle menu)
  1257. {
  1258.     DisableItem(menu, 0);
  1259. }
  1260.  
  1261.  
  1262. static void EnableMenu(MenuHandle menu)
  1263. {
  1264.     EnableItem(menu, 0);
  1265. }
  1266.  
  1267. static void RemoveMenuCheckMarks(MenuHandle menu)
  1268. {
  1269.     short item;
  1270.  
  1271.     for (item = 1; item <= CountMItems(menu); item++)
  1272.     {
  1273.         CheckItem(menu, item, false);
  1274.     };
  1275. }
  1276.  
  1277. static void RemoveMenuStyles(MenuHandle menu)
  1278. {
  1279.     short item;
  1280.  
  1281.     for (item = 1; item <= CountMItems(menu); item++)
  1282.     {
  1283.         SetItemStyle(menu, item, normal);
  1284.     };
  1285. }
  1286.  
  1287. static void EnableItemIf(MenuHandle menu, short item, Boolean condition)
  1288. {
  1289.     if (condition)
  1290.         EnableItem(menu, item);
  1291.     else
  1292.         DisableItem(menu, item);
  1293. }
  1294.  
  1295.  
  1296. // enable and disable menus based on the current state.
  1297. // In general, we set up the menu items only before calling MenuSelect or MenuKey,
  1298. // since these are the only times that a menu item can be selected. However, we
  1299. // also have to set up the menus whenever the front window changes because
  1300. // we enable and disable some menu titles depending on the front window.
  1301.  
  1302. void AdjustMenus(void)
  1303. {
  1304.     WindowPtr    window;
  1305.     Boolean        frontIsDAWindow;
  1306.     Boolean        frontIsDocWindow;
  1307.     Boolean        docHasSelection;
  1308.     Boolean        clipboardHasText;
  1309.     Boolean        haveContinuousFont;
  1310.     MenuHandle    menu;
  1311.     long        offset;
  1312.     TEHandle    te;
  1313.     TextStyle    theTextStyle;
  1314.     short        theFont;
  1315.     short        mode;
  1316.     short        item;
  1317.     
  1318.  
  1319.     // gather some state information that we'll need to decide which menu items to enable
  1320.     window = FrontWindow();
  1321.     frontIsDAWindow = (window != nil) && IsDAWindow(window);
  1322.     frontIsDocWindow = (window != nil) && IsDocumentWindow(window);
  1323.     if (frontIsDocWindow)
  1324.     {
  1325.         te = ((DocumentPeek) window)->docTE;
  1326.         docHasSelection = (*te)->selStart < (*te)->selEnd;
  1327.     }
  1328.     else
  1329.         docHasSelection = false;
  1330.     clipboardHasText = GetScrap(nil, 'TEXT', &offset)  > 0;
  1331.         // note that TEGetScrapLength works for the private TextEdit scrap only, which
  1332.         // is not used by styled TextEdit
  1333.     haveContinuousFont = false; // will really be set when setting up font menu
  1334.     
  1335.     // Apple menu is always enabled
  1336.     
  1337.     // File menu
  1338.     menu = GetMenuHandle(mFile);
  1339.     EnableItemIf(menu, iNew, gNumDocuments < kMaxOpenDocuments);
  1340.     EnableItemIf(menu, iClose, window != nil);
  1341.     EnableItemIf(menu, iPageSetup, frontIsDocWindow);
  1342.     EnableItemIf(menu, iPrint, frontIsDocWindow);
  1343.     EnableItem(menu, iQuit);
  1344.  
  1345.     menu = GetMenuHandle(mEdit);
  1346.     EnableItemIf(menu, iUndo, frontIsDAWindow); // can't handle Undo for documents yet
  1347.     EnableItemIf(menu, iCut, frontIsDAWindow || docHasSelection);
  1348.     EnableItemIf(menu, iCopy, frontIsDAWindow || docHasSelection);
  1349.     EnableItemIf(menu, iPaste, frontIsDAWindow || (frontIsDocWindow && clipboardHasText));
  1350.     EnableItemIf(menu, iClear, frontIsDAWindow || docHasSelection);
  1351.     EnableItemIf(menu, iSelectAll, frontIsDocWindow);
  1352.     
  1353.     menu = GetMenuHandle(mFont);
  1354.     RemoveMenuCheckMarks(menu);
  1355.     if (frontIsDocWindow)
  1356.     {
  1357.         EnableMenu(menu);
  1358.  
  1359.         mode = doFont;
  1360.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1361.         {
  1362.             Str255 theName, itemName;
  1363.             short itemCount;
  1364.             
  1365.             GetFontName(theTextStyle.tsFont, theName);
  1366.             itemCount = CountMItems(menu);
  1367.             for (item = 1; item <= itemCount; item++)
  1368.             {
  1369.                 GetMenuItemText(menu, item, itemName);
  1370.                 if (EqualString(theName, itemName, true, true))
  1371.                 {
  1372.                     CheckItem(menu, item, true);
  1373.                     break;
  1374.                 };
  1375.             };
  1376.             
  1377.             haveContinuousFont = true;
  1378.             theFont = theTextStyle.tsFont;
  1379.         };
  1380.     }
  1381.     else
  1382.     {
  1383.         DisableMenu(menu);
  1384.     };
  1385.     
  1386.     menu = GetMenuHandle (mFontSize);
  1387.     RemoveMenuCheckMarks(menu);
  1388.     RemoveMenuStyles(menu);
  1389.     if (frontIsDocWindow)
  1390.     {
  1391.         EnableMenu(menu);
  1392.  
  1393.         mode = doSize;
  1394.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1395.         {        
  1396.             switch (theTextStyle.tsSize)
  1397.             {
  1398.                 case 9: item = iNine; break;
  1399.                 case 10: item = iTen; break;
  1400.                 case 12: item = iTwelve; break;
  1401.                 case 14: item = iFourteen; break;
  1402.                 case 18: item = iEighteen; break;
  1403.                 case 24: item = iTwentyFour; break;
  1404.             };
  1405.             CheckItem(menu, item, true);
  1406.         };
  1407.  
  1408.         if (haveContinuousFont)
  1409.         {
  1410.             if (RealFont(theFont, 9))
  1411.                 SetItemStyle(menu, iNine, outline);
  1412.             if (RealFont(theFont, 10))
  1413.                 SetItemStyle(menu, iTen, outline);
  1414.             if (RealFont(theFont, 12))
  1415.                 SetItemStyle(menu, iTwelve, outline);
  1416.             if (RealFont(theFont, 14))
  1417.                 SetItemStyle(menu, iFourteen, outline);
  1418.             if (RealFont(theFont, 18))
  1419.                 SetItemStyle(menu, iEighteen, outline);
  1420.             if (RealFont(theFont, 24))
  1421.                 SetItemStyle(menu, iTwentyFour, outline);
  1422.         };
  1423.     }
  1424.     else
  1425.     {
  1426.         DisableMenu(menu);
  1427.     };
  1428.     
  1429.     menu = GetMenuHandle (mStyle);
  1430.     RemoveMenuCheckMarks(menu);
  1431.     if (frontIsDocWindow)
  1432.     {
  1433.         EnableMenu(menu);
  1434.  
  1435.         mode = doFace;
  1436.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1437.         {
  1438.             CheckItem(menu, iPlain, theTextStyle.tsFace == normal);
  1439.             CheckItem(menu, iBold, (theTextStyle.tsFace & bold) == bold);
  1440.             CheckItem(menu, iItalic, (theTextStyle.tsFace & italic) == italic);
  1441.             CheckItem(menu, iUnderline, (theTextStyle.tsFace & underline) == underline);
  1442.             CheckItem(menu, iOutline, (theTextStyle.tsFace & outline) == outline);
  1443.             CheckItem(menu, iShadow, (theTextStyle.tsFace & shadow) == shadow);
  1444.         };        
  1445.     }
  1446.     else
  1447.     {
  1448.         DisableMenu(menu);
  1449.     };
  1450.     
  1451.     DrawMenuBar();
  1452. }
  1453.  
  1454.  
  1455. /*    This is called when an item is chosen from the menu bar (after calling
  1456.     MenuSelect or MenuKey). It does the right thing for each command. */
  1457.  
  1458. void DoMenuCommand(long menuResult)
  1459. {
  1460.     short        menuID, menuItem;
  1461.     short        itemHit, daRefNum;
  1462.     Str255        daName;
  1463.     OSErr        saveErr;
  1464.     TEHandle    te;
  1465.     WindowPtr    window;
  1466.     long        scrapLength;
  1467.     long        offset;
  1468.     Handle        aHandle;
  1469.     long        oldSize, newSize;
  1470.     long        total, contig;
  1471.     TextStyle    theTextStyle;
  1472.     Str255        theFontName;
  1473.     short        theFontID;
  1474.     short        theFontSize;
  1475.     DocumentPeek theDocument;
  1476. #if qInline
  1477.     TSMDocumentID tsmDoc;
  1478. #endif // qInline
  1479.  
  1480.     window = FrontWindow();
  1481.     menuID = HiWord(menuResult);
  1482.     menuItem = LoWord(menuResult);
  1483.  
  1484. #if qInline
  1485.     if (menuID == 0)
  1486.         // no real menu command, so we don't want to confirm inline input text
  1487.         return;
  1488.     
  1489. #endif // qInline
  1490.     if (IsDocumentWindow(window))
  1491.     {
  1492.         theDocument = (DocumentPeek) window;
  1493.         te = theDocument->docTE;
  1494. #if qInline
  1495.  
  1496.         // for any real menu command, we should first confirm inline input text if there is any
  1497.         tsmDoc = theDocument->docTSMDoc;
  1498.         if (tsmDoc != nil)
  1499.             (void) FixTSMDocument(tsmDoc);
  1500. #endif // qInline
  1501.     };
  1502.     
  1503.     switch ( menuID ) {
  1504.         case mApple:
  1505.             switch ( menuItem ) {
  1506.                 case iAbout:        /* bring up alert for About */
  1507.                     itemHit = Alert(rAboutAlert, nil);
  1508.                     break;
  1509.                 default:            /* all non-About items in this menu are DAs et al */
  1510.                     /* type Str255 is an array in MPW 3 */
  1511.                     GetMenuItemText(GetMenuHandle(mApple), menuItem, daName);
  1512.                     daRefNum = OpenDeskAcc(daName);
  1513.                     AdjustMenus();
  1514.                     break;
  1515.             }
  1516.             break;
  1517.         case mFile:
  1518.             switch ( menuItem ) {
  1519.                 case iNew:
  1520.                     DoNew();
  1521.                     break;
  1522.                 case iClose:
  1523.                     (void) DoCloseWindow(FrontWindow());            /* ignore the result */
  1524.                     break;
  1525.                 case iPageSetup:
  1526.                     PrOpen();
  1527.                     if (PrError() == noErr)
  1528.                         (void) PrStlDialog(gPrinterRecord);
  1529.                     PrClose();
  1530.                     break;
  1531.                 case iPrint:
  1532.                     PrintText(te);
  1533.                     break;
  1534.                 case iQuit:
  1535. #if qAppleEvents
  1536.                     PrepareToQuit();
  1537. #else // qAppleEvents
  1538.                     Terminate();
  1539. #endif // qAppleEvents
  1540.                     break;
  1541.             }
  1542.             break;
  1543.         case mEdit:                    /* call SystemEdit for DA editing & MultiFinder */
  1544.             if ( !SystemEdit(menuItem-1) ) {
  1545.                 switch ( menuItem ) {
  1546.                     case iCut:
  1547.                         if ( ZeroScrap() == noErr )
  1548.                         {
  1549.                             PurgeSpace(&total, &contig);
  1550.                             if ((*te)->selEnd - (*te)->selStart + kTESlop > contig)
  1551.                                 AlertUser(eNoSpaceCut);
  1552.                             else
  1553.                             {
  1554.                                 TECut(te);
  1555.                                 theDocument->modified = true;
  1556.                             };
  1557.                         };
  1558.                         break;
  1559.                     case iCopy:
  1560.                         if ( ZeroScrap() == noErr )
  1561.                             TECopy(te);
  1562.                         break;
  1563.                     case iPaste:
  1564.                         scrapLength = GetScrap(nil, 'TEXT', &offset);
  1565.                         if ( scrapLength + ((*te)->teLength -
  1566.                                 ((*te)->selEnd - (*te)->selStart)) > kMaxTELength )
  1567.                             AlertUser(eExceedPaste);
  1568.                         else
  1569.                         {
  1570.                             aHandle = (Handle) TEGetText(te);
  1571.                             oldSize = GetHandleSize(aHandle);
  1572.                             newSize = oldSize + scrapLength + kTESlop;
  1573.                             SetHandleSize(aHandle, newSize);
  1574.                             saveErr = MemError();
  1575.                             SetHandleSize(aHandle, oldSize);
  1576.                             if (saveErr != noErr)
  1577.                                 AlertUser(eNoSpacePaste);
  1578.                             else
  1579.                             {
  1580.                                 TEStylePaste(te);
  1581.                                 theDocument->modified = true;
  1582.                             };
  1583.                         };
  1584.                         break;
  1585.                     case iClear:
  1586.                         TEDelete(te);
  1587.                         theDocument->modified = true;
  1588.                         break;
  1589.                     case iSelectAll:
  1590.                         TESetSelect(0, (*te)->teLength, te);
  1591.                         break;
  1592.                 };
  1593.             if (menuItem != iCopy)
  1594.                 AdjustScrollbars(window, false);
  1595.             }
  1596.             break;
  1597.         case mFont:
  1598.             GetMenuItemText(GetMenuHandle(mFont), menuItem, theFontName);
  1599.             GetFNum(theFontName, &theFontID);
  1600.             theTextStyle.tsFont = theFontID;
  1601.             TESetStyle(doFont, &theTextStyle, true, te);
  1602.             if ((*te)->selEnd - (*te)->selStart > 0)
  1603.                 theDocument->modified = true;
  1604.             AdjustScrollbars(window, false);
  1605.             break;
  1606.         case mFontSize:
  1607.             switch (menuItem)
  1608.             {
  1609.                 case iNine:
  1610.                     theFontSize = 9;
  1611.                     break;
  1612.                 case iTen:
  1613.                     theFontSize = 10;
  1614.                     break;
  1615.                 case iTwelve:
  1616.                     theFontSize = 12;
  1617.                     break;
  1618.                 case iFourteen:
  1619.                     theFontSize = 14;
  1620.                     break;
  1621.                 case iEighteen:
  1622.                     theFontSize = 18;
  1623.                     break;
  1624.                 case iTwentyFour:
  1625.                     theFontSize = 24;
  1626.                     break;
  1627.             };
  1628.             theTextStyle.tsSize = theFontSize;
  1629.             TESetStyle(doSize, &theTextStyle, true, te);
  1630.             if ((*te)->selEnd - (*te)->selStart > 0)
  1631.                 theDocument->modified = true;
  1632.             AdjustScrollbars(window, false);
  1633.             break;
  1634.         case mStyle:
  1635.             switch (menuItem)
  1636.             {
  1637.                 case iPlain:
  1638.                     *((short *) &theTextStyle.tsFace) = 0; // see technote TE 16
  1639.                     theTextStyle.tsFace = normal;
  1640.                     break;
  1641.                 case iBold:
  1642.                     theTextStyle.tsFace = bold;
  1643.                     break;
  1644.                 case iItalic:
  1645.                     theTextStyle.tsFace = italic;
  1646.                     break;
  1647.                 case iUnderline:
  1648.                     theTextStyle.tsFace = underline;
  1649.                     break;
  1650.                 case iOutline:
  1651.                     theTextStyle.tsFace = outline;
  1652.                     break;
  1653.                 case iShadow:
  1654.                     theTextStyle.tsFace = shadow;
  1655.                     break;
  1656.             };
  1657.             if (menuItem == iPlain)
  1658.                 TESetStyle(doFace, &theTextStyle, true, te); // doToggle doesn't work for plain
  1659.             else
  1660.                 TESetStyle(doFace + doToggle, &theTextStyle, true, te);
  1661.             if ((*te)->selEnd - (*te)->selStart > 0)
  1662.                 theDocument->modified = true;
  1663.             AdjustScrollbars(window, false);
  1664.             break;
  1665.     };
  1666.     HiliteMenu(0);                    /* unhighlight what MenuSelect (or MenuKey) hilited */
  1667. }
  1668.  
  1669.  
  1670. /* Create a new document and window. */
  1671.  
  1672. void DoNew(void)
  1673. {
  1674.     Boolean        good;
  1675.     Ptr            storage;
  1676.     WindowPtr    window;
  1677.     Rect        destRect, viewRect;
  1678.     DocumentPeek doc;
  1679. #if qInline
  1680.     OSType        supportedInterfaces[1];
  1681. #endif // qInline
  1682.  
  1683.     storage = NewPtr(sizeof(DocumentRecord));
  1684.     if ( storage != nil ) {
  1685.         doc = (DocumentPeek) storage;
  1686.         doc->modified = false;
  1687. #if qInline
  1688.         doc->docTSMTERecHandle = nil;
  1689.         doc->docTSMDoc = nil;
  1690. #endif // qInline
  1691.         window = GetNewWindow(rDocWindow, storage, (WindowPtr) -1);
  1692.         if ( window != nil ) {
  1693.             gNumDocuments += 1;            /* this will be decremented when we call DoCloseWindow */
  1694.             good = false;
  1695.             SetPort(window);
  1696.             
  1697.             // on a Roman system, the default text size is 0; we need a real size to make the Size menu work
  1698.             TextSize(GetDefFontSize());
  1699.             GetTERect(window, &viewRect);
  1700.             destRect = viewRect;
  1701.             destRect.right = destRect.left + kMaxDocWidth;
  1702.             doc->docTE = TEStyleNew(&destRect, &viewRect);
  1703.             good = doc->docTE != nil;    /* if TENew succeeded, we have a good document */
  1704.             if ( good ) {                /* 1.02 - good document? — proceed */
  1705.                 TEAutoView(true, doc->docTE);
  1706.                 doc->docClick = (*doc->docTE)->clickLoop;
  1707. #if powerc
  1708.                 TESetClickLoop(gClickLoopUPP, doc->docTE);
  1709. #else
  1710.                 (*doc->docTE)->clickLoop = (TEClickLoopUPP) AsmClickLoopProc;
  1711. #endif
  1712.             }
  1713.             
  1714.             if ( good ) {                /* good document? — get scrollbars */
  1715.                 doc->docVScroll = GetNewControl(rVScroll, window);
  1716.                 good = (doc->docVScroll != nil);
  1717.             }
  1718.             if ( good) {
  1719.                 doc->docHScroll = GetNewControl(rHScroll, window);
  1720.                 good = (doc->docHScroll != nil);
  1721.             }
  1722. #if qInline
  1723.             if (good && gHasTSMTE)
  1724.             {
  1725.                 supportedInterfaces[0] = kTSMTEInterfaceType;
  1726.                 if (NewTSMDocument(1, supportedInterfaces, &doc->docTSMDoc,
  1727.                             (long) &doc->docTSMTERecHandle) == noErr)
  1728.                 {
  1729.                     TSMTERecPtr tsmteRecPtr = *(doc->docTSMTERecHandle);
  1730.                     
  1731.                     tsmteRecPtr->textH = doc->docTE;
  1732.                     tsmteRecPtr->preUpdateProc = gTSMTEPreUpdateUPP;
  1733.                     tsmteRecPtr->postUpdateProc = gTSMTEPostUpdateUPP;
  1734.                     tsmteRecPtr->updateFlag = kTSMTEAutoScroll;
  1735.                     tsmteRecPtr->refCon = (long) window;
  1736.                 }
  1737.                 else
  1738.                     good = false;
  1739.             };
  1740. #endif // qInline
  1741.  
  1742.             if ( good ) {                /* good? — adjust & draw the controls, draw the window */
  1743.                 /* false to AdjustScrollValues means musn’t redraw; technically, of course,
  1744.                 the window is hidden so it wouldn’t matter whether we called ShowControl or not. */
  1745.                 AdjustScrollValues(window, false);
  1746.                 ShowWindow(window);
  1747.                 AdjustMenus();
  1748.             } else {
  1749.                 DoCloseWindow(window);    /* otherwise regret we ever created it... */
  1750.                 AlertUser(eNoWindow);            /* and tell user */
  1751.             }
  1752.         } else
  1753.             DisposePtr(storage);            /* get rid of the storage if it is never used */
  1754.     }
  1755. } /*DoNew*/
  1756.  
  1757.  
  1758. /* Close a window. This handles desk accessory and application windows. */
  1759.  
  1760. /*    1.01 - At this point, if there was a document associated with a
  1761.     window, you could do any document saving processing if it is 'dirty'.
  1762.     DoCloseWindow would return true if the window actually closed, i.e.,
  1763.     the user didn’t cancel from a save dialog. This result is handy when
  1764.     the user quits an application, but then cancels the save of a document
  1765.     associated with a window. */
  1766.  
  1767. Boolean DoCloseWindow(WindowPtr window)
  1768. {
  1769.     DocumentPeek theDocument;
  1770.  
  1771.     if ( IsDAWindow(window) )
  1772.         CloseDeskAcc(((WindowPeek) window)->windowKind);
  1773.     else if ( IsDocumentWindow(window) ) {
  1774.         // ??? check modified flag whether document needs saving
  1775.         theDocument = (DocumentPeek) window;
  1776. #if qInline
  1777.         if (theDocument->docTSMDoc != nil)
  1778.         {
  1779.             (void) FixTSMDocument(theDocument->docTSMDoc);
  1780.             // DeleteTSMDocument might cause crash if we don't deactivate first, so...
  1781.             (void) DeactivateTSMDocument(theDocument->docTSMDoc);
  1782.             (void) DeleteTSMDocument(theDocument->docTSMDoc);
  1783.         };
  1784. #endif // qInline
  1785.         if (theDocument->docTE != nil)
  1786.             TEDispose(theDocument->docTE);
  1787.         /*    1.01 - We used to call DisposeWindow, but that was technically
  1788.             incorrect, even though we allocated storage for the window on
  1789.             the heap. We should instead call CloseWindow to have the structures
  1790.             taken care of and then dispose of the storage ourselves. */
  1791.         CloseWindow(window);
  1792.         DisposePtr((Ptr) window);
  1793.         gNumDocuments -= 1;
  1794.     }
  1795.     AdjustMenus();
  1796.     return true;
  1797. } /*DoCloseWindow*/
  1798.  
  1799.  
  1800. // PrintText prints the text in the edit record. It opens a printer port, calculates
  1801. // the number of lines per page (which may be different for each page depending on the
  1802. // text styles) and then calls TEUpdate for the page, scrolls a page and calls TEUpdate,
  1803. // etc.
  1804.  
  1805. static void PrintText(TEHandle theText)
  1806. {
  1807.     const short kMargin = 20; // page margins in pixels
  1808.     const Rect zeroRect = { 0, 0, 0, 0 };
  1809.     short totalLines;
  1810.     GrafPtr oldPort;
  1811.     Rect oldViewRect;
  1812.     Rect oldDestRect;
  1813.     Rect viewRect;
  1814.     Rect updateRect;
  1815.     Rect clipRect;
  1816.     short totalHeight;
  1817.     short currentLine;
  1818.     short scrollAmount;
  1819.     TPrStatus thePrinterStatus;
  1820.     Boolean printManagerIsOpen = false;
  1821.     Boolean userHasCancelled = false;
  1822.     short viewHeight;
  1823.     TPPrPort thePrinterPort;
  1824.     
  1825.     if (gPrinterRecord != nil)
  1826.     {
  1827.         PrOpen();
  1828.         if (PrJobDialog(gPrinterRecord))
  1829.         {
  1830.             GetPort(&oldPort);
  1831.             oldViewRect = (*theText)->viewRect;
  1832.             oldDestRect = (*theText)->destRect;
  1833.             thePrinterPort = PrOpenDoc(gPrinterRecord, nil, nil);
  1834.             printManagerIsOpen = (PrError() == noErr);
  1835.         };
  1836.     };
  1837.     
  1838.     if (printManagerIsOpen)
  1839.     {
  1840.         SetPort((GrafPtr) thePrinterPort);
  1841.         
  1842.         // re-wrap the text to fill the entire page minus margins
  1843.         viewRect = (*gPrinterRecord)->prInfo.rPage;
  1844.         InsetRect(&viewRect, kMargin, kMargin);
  1845.         (*theText)->inPort = (GrafPtr) thePrinterPort;
  1846.         (*theText)->destRect = viewRect;
  1847.         (*theText)->viewRect = viewRect;
  1848.         TECalText(theText);
  1849.         totalLines = (*theText)->nLines;
  1850.         totalHeight = TEGetHeight(totalLines, 0, theText);
  1851.         (*theText)->destRect.bottom = (*theText)->destRect.top + totalHeight;
  1852.         
  1853.         currentLine = 1;
  1854.         
  1855.         while ((!userHasCancelled) && (currentLine <= totalLines))
  1856.         {
  1857.             PrOpenPage(thePrinterPort, nil);
  1858.             scrollAmount = 0;
  1859.             clipRect = (*gPrinterRecord)->prInfo.rPage;
  1860.             ClipRect(&clipRect);
  1861.             
  1862.             viewHeight = (*theText)->viewRect.bottom - (*theText)->viewRect.top + 1;
  1863.             
  1864.             while (((scrollAmount + TEGetHeight(currentLine, currentLine, theText)) <= viewHeight)
  1865.                         && (currentLine <= totalLines))
  1866.             {
  1867.                 scrollAmount += TEGetHeight(currentLine, currentLine, theText);
  1868.                 currentLine++;
  1869.             };
  1870.             
  1871.             (*theText)->viewRect.bottom = scrollAmount + kMargin;
  1872.             TEDeactivate(theText); // avoid printing selections
  1873.             updateRect = (*theText)->viewRect;
  1874.             TEUpdate(&updateRect, theText);
  1875.             ClipRect(&zeroRect); // prevent TEScroll from redrawing the text
  1876.             TEScroll(0, -scrollAmount, theText); // scroll so we can print the next page
  1877.             (*theText)->viewRect.bottom = viewRect.bottom; // reset to full page;
  1878.             
  1879.             if (PrError() == iPrAbort)
  1880.                 userHasCancelled = true;
  1881.             PrClosePage(thePrinterPort);
  1882.         };
  1883.         
  1884.         PrCloseDoc(thePrinterPort);
  1885.  
  1886.         if ((*gPrinterRecord)->prJob.bJDocLoop == bSpoolLoop && PrError() == noErr)
  1887.             PrPicFile(gPrinterRecord, nil, nil, nil, &thePrinterStatus);
  1888.         PrClose();
  1889.         
  1890.         SetPort(oldPort);
  1891.         (*theText)->inPort = oldPort;
  1892.         (*theText)->viewRect = oldViewRect;
  1893.         (*theText)->destRect = oldDestRect;
  1894.         TECalText(theText);
  1895.         updateRect = (*theText)->viewRect;
  1896.         TEUpdate(&updateRect, theText);
  1897.     };
  1898. }
  1899.  
  1900. #if qAppleEvents
  1901.  
  1902. // handle the Quit menu command or Apple event by closing all windows, and
  1903. // setting gQuitting if successful.
  1904.  
  1905. static void PrepareToQuit(void)
  1906. {
  1907.     WindowPtr aWindow;
  1908.     
  1909.     gQuitting = true;
  1910.     aWindow = FrontWindow();
  1911.  
  1912.     while (gQuitting && (aWindow != nil))
  1913.     {
  1914.         gQuitting = DoCloseWindow(aWindow);
  1915.         aWindow = FrontWindow();
  1916.     };
  1917. }
  1918.  
  1919. #if qInline
  1920.  
  1921. static void ExitApplication(void)
  1922. {
  1923.     if (gHasTextServices)
  1924.         (void) CloseTSMAwareApplication();
  1925.  
  1926.     // set global fontForce flag so other apps don't get confused
  1927.     (void) SetScriptManagerVariable(smFontForce, gSavedFontForce);
  1928.  
  1929.     ExitToShell();
  1930. }
  1931.  
  1932. #endif // qInline
  1933. #else // qAppleEvents
  1934.  
  1935. // handle the Quit menu command by closing all windows, and quitting if successful.
  1936.  
  1937. static void Terminate(void)
  1938. {
  1939.     WindowPtr aWindow;
  1940.     Boolean closed;
  1941.     
  1942.     closed = true;
  1943.     aWindow = FrontWindow();
  1944.  
  1945.     while (closed && (aWindow != nil))
  1946.     {
  1947.         closed = DoCloseWindow(aWindow);
  1948.         aWindow = FrontWindow();
  1949.     };
  1950.  
  1951.     if (closed)
  1952.         ExitToShell(); // exit if no cancellation
  1953. }
  1954.  
  1955. #endif // qAppleEvents
  1956.  
  1957. /* Return a rectangle that is inset from the portRect by the size of
  1958.     the scrollbars and a little extra margin. */
  1959.  
  1960. void GetTERect(WindowPtr window, Rect *teRect)
  1961. {
  1962.     *teRect = window->portRect;
  1963.     InsetRect(teRect, kTextMargin, kTextMargin);    /* adjust for margin */
  1964.     teRect->bottom = teRect->bottom - 15;        /* and for the scrollbars */
  1965.     teRect->right = teRect->right - 15;
  1966. } /*GetTERect*/
  1967.  
  1968.  
  1969. /* Update the TERec's view rect so that it is the greatest multiple of
  1970.     the lineHeight that still fits in the old viewRect. */
  1971.  
  1972. void AdjustViewRect(TEHandle docTE)
  1973. {
  1974.     TEPtr        te;
  1975.     
  1976.     te = *docTE;
  1977.     te->viewRect.bottom = (((te->viewRect.bottom - te->viewRect.top) / te->lineHeight)
  1978.                             * te->lineHeight) + te->viewRect.top;
  1979. } /*AdjustViewRect*/
  1980.  
  1981.  
  1982. /* Scroll the TERec around to match up to the potentially updated scrollbar
  1983.     values. This is really useful when the window has been resized such that the
  1984.     scrollbars became inactive but the TERec was already scrolled. */
  1985.  
  1986. void AdjustTE(WindowPtr window)
  1987. {
  1988.     TEPtr        te;
  1989.     
  1990.     te = *((DocumentPeek)window)->docTE;
  1991.     TEScroll((te->viewRect.left - te->destRect.left) -
  1992.             GetControlValue(((DocumentPeek)window)->docHScroll),
  1993.             (te->viewRect.top - te->destRect.top) -
  1994.                 GetControlValue(((DocumentPeek)window)->docVScroll),
  1995.             ((DocumentPeek)window)->docTE);
  1996. } /*AdjustTE*/
  1997.  
  1998.  
  1999. /* Calculate the new control maximum value and current value, whether it is the horizontal or
  2000.     vertical scrollbar. The vertical max is calculated by comparing the number of lines to the
  2001.     vertical size of the viewRect. The horizontal max is calculated by comparing the maximum document
  2002.     width to the width of the viewRect. The current values are set by comparing the offset between
  2003.     the view and destination rects. If necessary and we canRedraw, have the control be re-drawn by
  2004.     calling ShowControl. */
  2005.  
  2006. // for styled text, we cannot rely on the line height being constant, so we
  2007. // have to use pixels instead of nLines
  2008.  
  2009. void AdjustHV(Boolean isVert, ControlHandle control, TEHandle docTE, Boolean canRedraw)
  2010. {
  2011.     short        value, max;
  2012.     short        oldValue, oldMax;
  2013.     
  2014.     oldValue = GetControlValue(control);
  2015.     oldMax = GetControlMaximum(control);
  2016.     if (isVert)
  2017.         max = TEGetHeight((*docTE)->nLines, 0, docTE) -
  2018.                     ((*docTE)->viewRect.bottom - (*docTE)->viewRect.top);
  2019.     else
  2020.         max = kMaxDocWidth - ((*docTE)->viewRect.right - (*docTE)->viewRect.left);
  2021.     if (max < 0)
  2022.         max = 0;
  2023.     SetControlMaximum(control, max);
  2024.     if (isVert)
  2025.         value = (*docTE)->viewRect.top - (*docTE)->destRect.top;
  2026.     else
  2027.         value = (*docTE)->viewRect.left - (*docTE)->destRect.left;
  2028.     
  2029.     if ( value < 0 )
  2030.         value = 0;
  2031.     else if ( value >  max ) value = max;
  2032.     
  2033.     SetControlValue(control, value);
  2034.     /* now redraw the control if it needs to be and can be */
  2035.     if ( canRedraw || (max != oldMax) || (value != oldValue) )
  2036.         ShowControl(control);
  2037. } /*AdjustHV*/
  2038.  
  2039.  
  2040. /* Simply call the common adjust routine for the vertical and horizontal scrollbars. */
  2041.  
  2042. void AdjustScrollValues(WindowPtr window, Boolean canRedraw)
  2043. {
  2044.     DocumentPeek doc;
  2045.     
  2046.     doc = (DocumentPeek)window;
  2047.     AdjustHV(true, doc->docVScroll, doc->docTE, canRedraw);
  2048.     AdjustHV(false, doc->docHScroll, doc->docTE, canRedraw);
  2049. } /*AdjustScrollValues*/
  2050.  
  2051.  
  2052. /*    Re-calculate the position and size of the viewRect and the scrollbars.
  2053.     kScrollTweek compensates for off-by-one requirements of the scrollbars
  2054.     to have borders coincide with the growbox. */
  2055.  
  2056. void AdjustScrollSizes(WindowPtr window)
  2057. {
  2058.     Rect        teRect;
  2059.     DocumentPeek doc;
  2060.     
  2061.     doc = (DocumentPeek) window;
  2062.     GetTERect(window, &teRect);                            /* start with TERect */
  2063.     (*doc->docTE)->viewRect = teRect;
  2064.     MoveControl(doc->docVScroll, window->portRect.right - kScrollbarAdjust, -1);
  2065.     SizeControl(doc->docVScroll, kScrollbarWidth, (window->portRect.bottom - 
  2066.                 window->portRect.top) - (kScrollbarAdjust - kScrollTweek));
  2067.     MoveControl(doc->docHScroll, -1, window->portRect.bottom - kScrollbarAdjust);
  2068.     SizeControl(doc->docHScroll, (window->portRect.right - 
  2069.                 window->portRect.left) - (kScrollbarAdjust - kScrollTweek),
  2070.                 kScrollbarWidth);
  2071. } /*AdjustScrollSizes*/
  2072.  
  2073.  
  2074. /* Turn off the controls by jamming a zero into their contrlVis fields (HideControl erases them
  2075.     and we don't want that). If the controls are to be resized as well, call the procedure to do that,
  2076.     then call the procedure to adjust the maximum and current values. Finally re-enable the controls
  2077.     by jamming a $FF in their contrlVis fields. */
  2078.  
  2079. void AdjustScrollbars(WindowPtr window, Boolean needsResize)
  2080. {
  2081.     DocumentPeek doc;
  2082.     
  2083.     doc = (DocumentPeek) window;
  2084.     /* First, turn visibility of scrollbars off so we won’t get unwanted redrawing */
  2085.     (*doc->docVScroll)->contrlVis = kControlInvisible;    /* turn them off */
  2086.     (*doc->docHScroll)->contrlVis = kControlInvisible;
  2087.     if ( needsResize )                                    /* move & size as needed */
  2088.         AdjustScrollSizes(window);
  2089.     AdjustScrollValues(window, needsResize);            /* fool with max and current value */
  2090.     /* Now, restore visibility in case we never had to ShowControl during adjustment */
  2091.     (*doc->docVScroll)->contrlVis = kControlVisible;    /* turn them on */
  2092.     (*doc->docHScroll)->contrlVis = kControlVisible;
  2093. } /* AdjustScrollbars */
  2094.  
  2095.  
  2096. // When the user selects text by dragging, TextEdit repeatedly calls a click loop routine which
  2097. // it gets from the TERecord's clikLoop field. TextEdit's default routine does some useful things,
  2098. // such as scrolling the text being selected, but it doesn't know about our scroll bars.
  2099. // Therefore, we replace the routine with one that calls both the old routine and an add-on routine
  2100. // which handles the scroll bars. Unfortunately, the way this works is very different for 68K and
  2101. // PowerPC. On 68K, we have to be aware that the original click loop routine has a register-based
  2102. // interface, so our replacement is easier to write in assembly. For PowerPC, we can let routine
  2103. // descriptors handle the argument conversions, and do everything in the C routine ClickLoopProc.
  2104.  
  2105. #if powerc
  2106. pascal Boolean ClickLoopProc(TEPtr pTE)
  2107. {
  2108.     CallTEClickLoopProc(GetOldClickLoop(), pTE);
  2109.     ClickLoopAddOn();
  2110.     return true;
  2111. }
  2112. #endif
  2113.  
  2114.  
  2115. // The ClickLoopAddOn routine handles the scroll bars during drag-scrolling.
  2116.  
  2117. pascal void ClickLoopAddOn(void)
  2118. {
  2119.     WindowPtr    window;
  2120.     RgnHandle    region;
  2121.     
  2122.     window = FrontWindow();
  2123.     region = NewRgn();
  2124.     GetClip(region);                    /* save clip */
  2125.     ClipRect(&window->portRect);
  2126.     AdjustScrollValues(window, true);    /* pass true for canRedraw */
  2127.     SetClip(region);                    /* restore clip */
  2128.     DisposeRgn(region);
  2129. }
  2130.  
  2131.  
  2132. // GetOldClickLoop returns the address of the default click loop routine that we put into the
  2133. // TERec when creating it.
  2134.  
  2135. pascal TEClickLoopUPP GetOldClickLoop(void)
  2136. {
  2137.     return ((DocumentPeek)FrontWindow())->docClick;
  2138. }
  2139.  
  2140.  
  2141. // Check whether a window is a document window created by the application.
  2142. // These windows have the windowKind userKind, so we can distinguish them from
  2143. // desk accessories, dialogs, and other windows.
  2144.  
  2145. Boolean IsDocumentWindow(WindowPtr window)
  2146. {
  2147.     return (window != nil) && (((WindowPeek) window)->windowKind == userKind);
  2148. }
  2149.  
  2150.  
  2151. // Check whether a window belongs to a desk accessory.
  2152. // These windows have negative windowKinds.
  2153.  
  2154. Boolean IsDAWindow(WindowPtr window)
  2155. {
  2156.     return (window != nil) && (((WindowPeek) window)->windowKind < 0);
  2157. }
  2158.  
  2159.  
  2160. /*    Display an alert that tells the user an error occurred, then exit the program.
  2161.     This routine is used as an ultimate bail-out for serious errors that prohibit
  2162.     the continuation of the application. Errors that do not require the termination
  2163.     of the application should be handled in a different manner. Error checking and
  2164.     reporting has a place even in the simplest application. The error number is used
  2165.     to index an 'STR#' resource so that a relevant message can be displayed. */
  2166.  
  2167. void AlertUser(short error)
  2168. {
  2169.     short        itemHit;
  2170.     Str255        message;
  2171.  
  2172.     SetCursor(&qd.arrow);
  2173.     /* type Str255 is an array in MPW 3 */
  2174.     GetIndString(message, rErrorStrings, error);
  2175.     ParamText(message, (ConstStr255Param) "", (ConstStr255Param) "", (ConstStr255Param) "");
  2176.     itemHit = Alert(rUserAlert, nil);
  2177. } /* AlertUser */
  2178.  
  2179. #if qAppleEvents
  2180.  
  2181. // Apple Event Support
  2182.  
  2183. static OSErr GotRequiredParameters(const AppleEvent *theAppleEvent)
  2184. {
  2185.     OSErr myErr;
  2186.     DescType returnedType;
  2187.     Size actualSize;
  2188.     
  2189.     myErr = AEGetAttributePtr(theAppleEvent, keyMissedKeywordAttr, typeWildCard, &returnedType,
  2190.                 nil, 0, &actualSize);
  2191.     if (myErr == errAEDescNotFound)
  2192.         return noErr;
  2193.     else if (myErr == noErr)
  2194.         return errAEParamMissed;
  2195.     else
  2196.         return myErr;
  2197. }
  2198.  
  2199. pascal OSErr HandleOAppEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2200. {
  2201.     #pragma unused(reply, refCon)
  2202.     
  2203.     OSErr theError;
  2204.     
  2205.     theError = GotRequiredParameters(theEvent);
  2206.     if (theError == noErr)
  2207.         DoNew();
  2208.     return theError;
  2209. }
  2210.  
  2211. pascal OSErr HandleDocEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2212. {
  2213.     #pragma unused(theEvent, reply, refCon)
  2214.     
  2215.     OSErr theError;
  2216.     AEDescList docList;
  2217.     long itemsInList;
  2218.     long index;
  2219.     AEKeyword keyword;
  2220.     DescType returnedType;
  2221.     FSSpec theFileSpec;
  2222.     Size actualSize;
  2223.     
  2224.     
  2225.     theError = AEGetParamDesc(theEvent, keyDirectObject, typeAEList,  &docList);
  2226.     if (theError == noErr)
  2227.     {
  2228.         theError = GotRequiredParameters(theEvent);
  2229.         if (theError == noErr)
  2230.         {
  2231.             theError = AECountItems(&docList, &itemsInList);
  2232.             if (theError == noErr)
  2233.             {
  2234.                 for (index = 1; index <= itemsInList; index++)
  2235.                 {
  2236.                     theError = AEGetNthPtr(&docList, index, typeFSS, &keyword, &returnedType,
  2237.                                 (Ptr) &theFileSpec, sizeof(theFileSpec), &actualSize);
  2238.                     if (theError == noErr)
  2239.                     {
  2240.                         if (refCon == kAEOpenDocuments)
  2241.                             // we don't open documents yet, but here's what it would look like:
  2242.                             // theError = OpenDocument(theFileSpec);
  2243.                             ;
  2244.                         else
  2245.                             // we don't print disk documents either (we can't read them),
  2246.                             // but here's what it would look like:
  2247.                             // theError = PrintDocument(theFileSpec);
  2248.                             ;
  2249.                     };
  2250.                 };
  2251.             };
  2252.         };
  2253.         (void) AEDisposeDesc(&docList);
  2254.     };
  2255.     return theError;
  2256. }
  2257.  
  2258. pascal OSErr HandleQuitEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2259. {
  2260.     #pragma unused(reply, refCon)
  2261.     
  2262.     OSErr theError;
  2263.     
  2264.     theError = GotRequiredParameters(theEvent);
  2265.     if (theError == noErr)
  2266.     {
  2267.         PrepareToQuit();
  2268.         if (!gQuitting)
  2269.             theError = userCanceledErr;
  2270.     };
  2271.     return theError;
  2272. }
  2273.  
  2274. static OSErr InstallRequiredAppleEvents(void)
  2275. {
  2276.     OSErr result;
  2277.     
  2278.     gHandleOAppUPP = NewAEEventHandlerProc(HandleOAppEvent);
  2279.     FailNilUPP((UniversalProcPtr) gHandleOAppUPP);
  2280.     gHandleDocUPP = NewAEEventHandlerProc(HandleDocEvent);
  2281.     FailNilUPP((UniversalProcPtr) gHandleDocUPP);
  2282.     gHandleQuitUPP = NewAEEventHandlerProc(HandleQuitEvent);
  2283.     FailNilUPP((UniversalProcPtr) gHandleQuitUPP);
  2284.  
  2285.     result = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
  2286.                 gHandleOAppUPP, 0, false);
  2287.     if (result == noErr)
  2288.         result = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
  2289.                     gHandleDocUPP, kAEOpenDocuments, false);
  2290.     if (result == noErr)
  2291.         result = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
  2292.                     gHandleDocUPP, kAEPrintDocuments, false);
  2293.     if (result == noErr)
  2294.         result = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
  2295.                     gHandleQuitUPP, 0, false);
  2296.     return result;
  2297. }
  2298.  
  2299. #if qInline
  2300.  
  2301. static pascal void MyTSMTEPreUpdateProc(TEHandle textH, long refCon)
  2302. {
  2303.     #pragma unused(refCon)
  2304.  
  2305.     long response;
  2306.     ScriptCode keyboardScript;
  2307.     short mode;
  2308.     TextStyle theStyle;
  2309.     
  2310.     if ((Gestalt(gestaltTSMTEVersion, &response) == noErr) && (response == gestaltTSMTE1))
  2311.     {
  2312.         keyboardScript = GetScriptManagerVariable(smKeyScript);
  2313.         mode = doFont;
  2314.         if (!(TEContinuousStyle(&mode, &theStyle, textH) &&
  2315.                 FontToScript(theStyle.tsFont) == keyboardScript))
  2316.         {
  2317.             theStyle.tsFont = GetScriptVariable(keyboardScript, smScriptAppFond);
  2318.             TESetStyle(doFont, &theStyle, false, textH);
  2319.         };
  2320.     };
  2321. }
  2322.  
  2323. static pascal void MyTSMTEPostUpdateProc(TEHandle textH, long fixLen, long inputAreaStart,
  2324.             long inputAreaEnd, long pinStart, long pinEnd, long refCon)
  2325. {
  2326.     #pragma unused(textH, fixLen, inputAreaStart, inputAreaEnd, pinStart, pinEnd)
  2327.     
  2328.     AdjustScrollbars((WindowPtr) refCon, false);
  2329.     AdjustTE((WindowPtr) refCon);
  2330. }
  2331.  
  2332. #endif // qInline
  2333. #endif // qAppleEvents